package be.donovan.gui.helper;

import java.util.Map;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * service locator permits the decoupling of calling code from the
 * org.springframework.beans.factory.BeanFactory API
 * <p>
 * A sample config in an XML-based org.springframework.beans.factory.BeanFactory
 * look as follows :
 * 
 * <pre class="code">
 * &lt;beans&gt;
 * 
 *   &lt;bean class=&quot;be.mil.cccis.atdl3.config.ServiceLocator&quot; factory-method=&quot;getInstance&quot;/&gt;
 * 
 * lt;/beans&gt;
 * </pre>
 * 
 * <p>
 * The attendant <code>MyClientBean</code> class implementation might then
 * look something like this:
 * 
 * <pre class="code">
 * package a.b.c;
 * 
 * ublic class MyClientBean {
 * 
 *    public void someBusinessMethod() {
 *        
 *        MyService service = ServiceLocator.getService(MyService.class);
 *        // use the service object to effect the business logic...
 *    }
 * </pre>
 * 
 * @author wim
 * 
 */
public class ServiceLocator implements ApplicationContextAware, InitializingBean {

	private static final String SERVICE_LOCATOR_WAS_NOT_PROPERLY_INITIALIZED = "ServiceLocator was not properly initialized, initialize SpringContext";

	private ApplicationContext context;

	private Logger logger = LogManager.getLogger(getClass());

	private static final ServiceLocator instance = new ServiceLocator();

	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = applicationContext;
	}

	public void afterPropertiesSet() throws Exception {
		if(logger.isDebugEnabled()){
			logger.debug("initialized");
		}
	}

	public static ServiceLocator getInstance() {
		return instance;
	}

	/**
	 * Return a single bean of the given type or subtypes
	 * 
	 * @param serviceInterface
	 *            type of bean to match
	 * @return the matching bean instance
	 */
	@SuppressWarnings("unchecked")
	public <T>T locateService(Class<T> serviceInterface){
		T result = null; 
		if(this.getApplicationContext() == null)throw new IllegalStateException(SERVICE_LOCATOR_WAS_NOT_PROPERLY_INITIALIZED);
		if(logger.isDebugEnabled()){
			logger .debug("locate : "+serviceInterface);
		}
		try {
			result = (T) BeanFactoryUtils.beanOfType(this.getApplicationContext(),serviceInterface);
		} catch (NoSuchBeanDefinitionException e) {
			return locate2(serviceInterface);
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	<T>T locate2(Class<T> serviceInterface){
		if(this.getApplicationContext() == null)throw new IllegalStateException(SERVICE_LOCATOR_WAS_NOT_PROPERLY_INITIALIZED);
		if(logger.isDebugEnabled()){
			logger.debug("locate : "+serviceInterface);
		}
		Map beansOfTypeIncludingAncestors = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.getApplicationContext(),serviceInterface);
		for (Object entry : beansOfTypeIncludingAncestors.entrySet()) {
			T value = (T) ((Map.Entry)entry).getValue();
			if(serviceInterface.isInstance(value)){
				return value;
			}
			String valueClassName = value.getClass().getName();
			String className = serviceInterface.getName();
			if(valueClassName.equals(className)){
				return (T) value;
			}
		}
		return (T) BeanFactoryUtils.beanOfTypeIncludingAncestors(this.getApplicationContext(),serviceInterface);
	}
	

	@SuppressWarnings("unchecked")
	public <T> T locateByClassName(Class<T> serviceInterface) throws NoSuchBeanDefinitionException {
		T result = null;
		if (this.getApplicationContext() == null)
			throw new IllegalStateException(SERVICE_LOCATOR_WAS_NOT_PROPERLY_INITIALIZED);
		if(logger.isDebugEnabled()){
			logger.debug("locate : " + serviceInterface);
		}
		String[] matchingBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.getApplicationContext(),
				serviceInterface);
		for (String beanName : matchingBeanNames) {
			Object bean = getApplicationContext().getBean(beanName);
			if (bean.getClass().equals(serviceInterface)) {
				result = (T) bean;
			}
		}
		if (result == null) {
			throw new NoSuchBeanDefinitionException(serviceInterface.getClass().getName());
		}
		return result;
	}

	/**
	 * Return a single bean of the given type or subtypes
	 * 
	 * @param serviceInterface
	 *            type of bean to match
	 * @return the matching bean instance
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getService(Class<T> serviceInterface) {
		return getInstance().locateService(serviceInterface);
	}

	public static Object getBean(String name) {
		return getInstance().getBeanWithName(name);
	}

	private Object getBeanWithName(String name) {
		return getApplicationContext().getBean(name);
	}

	private ApplicationContext getApplicationContext() {
		return context;
	}

}
